import struct
import time

import serial


# CRC16 计算函数
def crc16(data: bytes) -> bytes:
    crc = 0xFFFF
    for byte in data:
        crc ^= byte
        for _ in range(8):
            if crc & 0x0001:
                crc >>= 1
                crc ^= 0xA001
            else:
                crc >>= 1
    return struct.pack('<H', crc)


# 构造 Modbus RTU 写单个寄存器（功能码 06）的报文
def modbus_rtu_write_single_register(slave_address, register_address, value):
    # 请求报文： 地址 + 功能码 + 寄存器地址 + 数据 + CRC
    # 1. 地址 (1字节)
    # 2. 功能码 (1字节)
    # 3. 寄存器地址 (2字节)
    # 4. 数据 (2字节)
    # 5. CRC (2字节)

    # 报文内容（前4个字节：从机地址、功能码、寄存器地址、数据）
    msg = struct.pack('>BBHH', slave_address, 0x06, register_address, value)

    # CRC 校验码
    crc = crc16(msg)

    # 将 CRC 加入报文
    msg += crc

    return msg


# 打印十六进制报文数据
def print_hex(data, label):
    """格式化并打印十六进制数据"""
    hex_data = ' '.join(f'{byte:02X}' for byte in data)
    print(f"{label}: {hex_data}")


# 解析报文并打印详细信息
def parse_modbus_rtu_response(response):
    """解析 Modbus RTU 响应报文"""
    if len(response) == 6:  # 功能码06的响应报文长度为6字节
        # 从机地址（1字节）
        slave_address = response[0]
        # 功能码（1字节）
        function_code = response[1]
        # 寄存器地址（2字节）
        register_address = struct.unpack('>H', response[2:4])[0]
        # 数据（2字节）
        value = struct.unpack('>H', response[4:6])[0]

        # CRC 校验码（2字节）
        crc_received = struct.unpack('<H', response[4:6])[0]

        print(f"解析结果:")
        print(f"  从机地址: {slave_address}")
        print(f"  功能码: {function_code}")
        print(f"  寄存器地址: {register_address:#06X}")
        print(f"  写入的值: {value:#06X}")
        print(f"  CRC 校验码: {crc_received:#06X}")
    else:
        print("无效的响应数据")

# 发送 Modbus RTU 报文并接收响应
def send_modbus_rtu_request(port, slave_address, register_address, value):
    # 打开串口
    with serial.Serial(port, baudrate=9600, timeout=1) as ser:
        # 构造请求报文
        request = modbus_rtu_write_single_register(slave_address, register_address, value)

        # 发送请求报文
        ser.write(request)
        print_hex(request, "发送报文")  # 格式化打印发送报文

        # 等待响应并读取
        response = ser.read(8)  # 最小响应长度为 8 字节
        print_hex(response, "接收到响应")  # 格式化打印接收到的响应

        # 解析响应报文
        parse_modbus_rtu_response(response)

        # 检查响应
        if len(response) == 6 and response[0] == slave_address and response[1] == 0x06:
            print(f"从机{slave_address}写入成功，寄存器地址：{register_address:#06X}，值：{value:#06X}")
        else:
            print("响应无效或写入失败")


# 测试
if __name__ == "__main__":
    # 串口端口，Windows 下可能是 COMx，Linux 下可能是 /dev/ttyUSB0
    port = 'COM7'

    while(True):
        for i in range(0, 4501, 450):

            value_to_write = i;

            print(f"写入数据：{value_to_write}")

            slave_address = 11
            register_address = 0x2001  # 寄存器地址
            send_modbus_rtu_request(port, slave_address, register_address, value_to_write)

            slave_address = 22
            send_modbus_rtu_request(port, slave_address, register_address, value_to_write)

            slave_address = 33
            send_modbus_rtu_request(port, slave_address, register_address, value_to_write)

            slave_address = 44
            send_modbus_rtu_request(port, slave_address, register_address, value_to_write)

            # time.sleep(2)

        for i in range(4500, -1, -450):
            value_to_write = i;

            slave_address = 11
            register_address = 0x2001  # 寄存器地址
            send_modbus_rtu_request(port, slave_address, register_address, value_to_write)

            slave_address = 22
            send_modbus_rtu_request(port, slave_address, register_address, value_to_write)

            slave_address = 33
            send_modbus_rtu_request(port, slave_address, register_address, value_to_write)

            slave_address = 44
            send_modbus_rtu_request(port, slave_address, register_address, value_to_write)

